package br.ufrn.btest;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.WordUtils;

import br.ufrn.btest.decorators.predicates.MyPredicate;
import br.ufrn.btest.parser.Machine;
import br.ufrn.btest.parser.Operation;
import br.ufrn.btest.utils.FileTools;

public class Report {

	
	private Operation operation;
	private List<Animation> animations;

	
	
	public Report(Operation operation, List<Animation> animations) {
		this.operation = operation;
		this.animations = animations;
	}

	
	
	public File generateReportFile() {
		String reportPath = getSourceMachineDirectory() + operation.getName() + "TestReport.txt";
		return FileTools.createFileWithContent(reportPath, generateReport());
	}

	
	
	private String generateReport() {
		StringBuffer testReport = new StringBuffer("");
		
		addHeader(testReport);
		
		if(operationHasReturnVariables()) {
			addTestForMethodsForOperationWithReturnVariables(testReport);
		} else {
			addTestForMethodsForOperationWithoutReturnVariables(testReport);
		}

		return testReport.toString();
	}
	
	
	
	private void addTestForMethodsForOperationWithReturnVariables(StringBuffer testReport) {
		int opCounter = 1;
		for (Animation animation : this.animations) {
			testReport.append(generatePreconditionComment(animation));
			String operationName = this.operation.getName() + "Test" + opCounter;
			Map<String, String> firstValuePossibility = animation.getValues().get(0);
			testReport.append(generateTestMethodForOperationWithReturn(firstValuePossibility, operationName, testReport));
			opCounter++;
		}
	}
	
	
	
	private String generatePreconditionComment(Animation animation) {
		StringBuffer comment = new StringBuffer("");
		String[] clauses = animation.getPredicate().toString().split("&");
		
		comment.append("/** Test Formula\n");
		
		for(int i = 0; i < clauses.length; i++) {
			if(i < clauses.length - 1) {
				comment.append(clauses[i].trim() + " &\n");
			} else {
				comment.append(clauses[i].trim() + "\n");
			}
		}
		
		comment.append("*/\n");
		
		return comment.toString();
	}



	private void addTestForMethodsForOperationWithoutReturnVariables(StringBuffer testReport) {
		int opCounter = 1;
		for (Animation animation : this.animations) {
			testReport.append(generatePreconditionComment(animation));
			String operationName = this.operation.getName() + "Test" + opCounter;
			Map<String, String> firstValuePossibility = animation.getValues().get(0);
			testReport.append(generateTestMethodForOperationWithoutReturn(firstValuePossibility, operationName, testReport));
			opCounter++;
		}
	}
	
	
	
	private String generateTestMethodForOperationWithReturn(Map<String, String> animation, String operationName, StringBuffer testReport) {
		StringBuffer testMethod = new StringBuffer("");

		Map<String, String> attributeValues = getAttributeValues(animation);
		Map<String, String> paramValues = getParamValues(animation);
		
		testMethod.append(operationName + "() {" + "\n");
		testMethod.append(getSetAttributes(attributeValues));
		testMethod.append(getCallForOperationWithReturn(paramValues));
		testMethod.append(getOracleVerificationForOperationWithReturn());

		return testMethod.toString();
	}
	
	
	
	private String generateTestMethodForOperationWithoutReturn(Map<String, String> animation, String operationName, StringBuffer testReport) {
		StringBuffer testMethod = new StringBuffer("");
		
		Map<String, String> attributeValues = getAttributeValues(animation);
		Map<String, String> paramValues = getParamValues(animation);
		
		testMethod.append(operationName + "() {" + "\n");
		testMethod.append(getSetAttributes(attributeValues));
		testMethod.append(getCallForOperationWithoutReturn(paramValues));
		testMethod.append(getOracleVerificationForOperationWithoutReturn(attributeValues));
		
		return testMethod.toString();
	}

	
	
	private String getSetAttributes(Map<String, String> attributeValues) {
		StringBuffer setMethods = new StringBuffer("");
		for (String att : attributeValues.keySet()) {
			setMethods.append("set_" + att + "(" + attributeValues.get(att) + ");" + "\n");
		}
		return setMethods.toString();
	}
	
	
	
	private String getCallForOperationWithReturn(Map<String, String> paramValues) {
		StringBuffer operationCall = new StringBuffer("");
		
		operationCall.append("returnValue = " + this.operation.getName() + "(");

		int paramCount = 1;
		for (String param : paramValues.keySet()) {
			if (paramCount < paramValues.size()) {
				operationCall.append(paramValues.get(param) + ", ");
			} else {
				operationCall.append(paramValues.get(param));
			}
			paramCount++;
		}

		operationCall.append(");" + "\n");
		
		return operationCall.toString();
	}
	
	
	
	private String getCallForOperationWithoutReturn(Map<String, String> paramValues) {
		StringBuffer operationCall = new StringBuffer("");
		
		operationCall.append(this.operation.getName() + "(");

		int paramCount = 1;
		for (String param : paramValues.keySet()) {
			if (paramCount < paramValues.size()) {
				operationCall.append(paramValues.get(param) + ", ");
			} else {
				operationCall.append(paramValues.get(param));
			}
			paramCount++;
		}

		operationCall.append(");" + "\n");
		
		return operationCall.toString();
	}
	
	
	
	private String getOracleVerificationForOperationWithReturn() {
		StringBuffer oracleVerification = new StringBuffer("");
		
		oracleVerification.append("// Oracle Value must be calculated" + "\n");
		oracleVerification.append("expectedValue = nil;" + "\n");
		oracleVerification.append("assertEquals(expectedValues, returnValue);" + "\n");
		oracleVerification.append("}");
		oracleVerification.append("\n\n");
		
		return oracleVerification.toString();
	}
	
	
	
	private String getOracleVerificationForOperationWithoutReturn(Map<String, String> attributeValues) {
		StringBuffer oracleVerification = new StringBuffer("");
		
		oracleVerification.append("// Oracle Value must be calculated" + "\n");
		
		for (String att : attributeValues.keySet()) {
			oracleVerification.append("expectedValueFor" + WordUtils.capitalize(att) + " = nil;\n");
			oracleVerification.append("actualValueFor" + WordUtils.capitalize(att) + " = get" + WordUtils.capitalize(att) + "()" +";\n");
			oracleVerification.append("assertEquals(" + "expectedValueFor" + WordUtils.capitalize(att) + ", " + "actualValueFor" + WordUtils.capitalize(att) + ");\n");
		}
		
		oracleVerification.append("}");
		oracleVerification.append("\n\n");
		
		return oracleVerification.toString();
	}
	
	
	
	private void addHeader(StringBuffer testReport) {
		testReport.append("TEST REPORT" + "\n");
		testReport.append("MACHINE: " + this.operation.getMachine().getName() + "\n");
	}
	

	
	// TODO: Find a better solution to create separate state variables and parameters.
	// TODO: refatorar!
	private HashMap<String, String> getAttributeValues(Map<String, String> animation) {
		HashMap<String, String> attributeValues = new HashMap<String, String>();
		
		if (this.operation.getMachine().getVariables() != null) {
			Set<String> variables = this.operation.getMachine().getVariables().getAll();
			
			Partitioner partitioner = new Partitioner(this.operation);
			Set<String> inputSpace = partitioner.getOperationInputSpace();
			
			if (this.operation.getMachine().getIncludes() != null) {
				for (Machine machineIncluded : this.operation.getMachine().getIncludes().getMachinesIncluded()) {
					for (String variable : machineIncluded.getVariables().getAll()) {
						if(inputSpace.contains(variable)) {
							variables.add(variable);
						}
					}
				}
			}
			
			for (String variable : variables) {
				attributeValues.put(variable, animation.get(variable));
			}
		}
		
		return attributeValues;
	}

	
	
	private HashMap<String, String> getParamValues(Map<String, String> animation) {
		HashMap<String, String> parameterValues = new HashMap<String, String>();
		List<String> parameters = operation.getParameters();

		for (String param : parameters) {
			parameterValues.put(param, animation.get(param));
		}

		return parameterValues;
	}
	
	
	
	private boolean operationHasReturnVariables() {
		boolean operationhasReturnVariables = !operation.getReturnVariables().isEmpty();
		return operationhasReturnVariables;
	}
	
	
	
	private String getSourceMachineDirectory() {
		String sourceMachineDirectory = operation.getMachine().getFile().getParent() + System.getProperty("file.separator");
		try {
			return URLDecoder.decode(sourceMachineDirectory, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return sourceMachineDirectory;
		}
	}
}
